Dieses Notebook bereitet die Daten für die Intelligent Zoning Engine vor. Es speichert

  • entities.geojson — Schulen, deren Geokoordinaten und Attribute: entity_id, capacity, und andere Attribute
  • entities.csv — Schulen und optimierungsrelevante Attribute: entity_id, capacity
  • units.geojson — Statistische Blöcke Berlins, deren Geometrie und Attribute
  • units.csv — Statsitische Blöcke Berlins und optimierungsrelevante Attribute: unit_id, population, percentage_sgb
  • weights.csv — optimierungsrelevante Gewichte wie Fußwege / Spalten: entity_id, unit_id, weight, value
  • assignment.csv — eine initiale Zuordnung / Spalten: unit_id, entity_id

Bereits in anderen scripten wurde vorbereitet:

  • Fußwegen von einer großen Sichprobe von (Wohn-)Gebäuden zu allen Schulen wurden berechnet und in route_matrix.csv gespeichert
  • Die Stichprobe wurde in sampled_buildings.csv gespeichert

Die Daten werden wiefolgt vorbereitet:

  • pro Block werden die Anzahl der einzuschulenden Kinder mit Hilfe der Einwohnerzahlen nach Alter auf LOR-Ebene in EWR201512E_Matrix.csv hochgerechnet
    • Kinder des LOR werden Anteilig nach Einwohnerzahl des Blocks im Verhältnis zum LOR auf die Blöcke verteilt
  • es werden minimale, durchschnittliche und maximale Fußwege aus jedem Block errechnet

TODO: - die sozioökonomischen Faktoren werden aus den Wahlbezirken auf die Blöcke hochgerechnet (https://github.com/berlinermorgenpost/cogran)

Laden der Daten

sampled_buildings = read_rds('output/sampled_buildings.rds')
bez = readOGR('download/RBS_OD_BEZ_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_BEZ_2015_12.geojson", layer: "OGRGeoJSON"
with 13 features
It has 2 fields
blk = readOGR('download/RBS_OD_BLK_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_BLK_2015_12.geojson", layer: "OGRGeoJSON"
with 15720 features
It has 4 fields
lor = readOGR('download/RBS_OD_LOR_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_LOR_2015_12.geojson", layer: "OGRGeoJSON"
with 447 features
It has 8 fields
re_schulstand = readOGR('download/re_schulstand.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/re_schulstand.geojson", layer: "OGRGeoJSON"
with 709 features
It has 20 fields
HKO_2015 = readOGR('output/HKO_2015.geojson', 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "output/HKO_2015.geojson", layer: "OGRGeoJSON"
with 306019 features
It has 4 fields

Schulwege

route_matrix = read_rds('output/route_matrix.rds')

Schulkapazitäten und Einwohnerzahler auf LOR-Ebene

kapas = read_csv('download/anmeldezahlen.csv') %>% filter(grepl('G', Schulnummer)) %>% filter(!is.na(`Plätze`))
Parsed with column specification:
cols(
  Bezirk = col_character(),
  Schulnummer = col_character(),
  Schulname = col_character(),
  Plätze = col_integer(),
  Anmeldungen = col_character()
)
einwohner_lor = read_delim('download/EWR201512E_Matrix.csv', delim=';')
Parsed with column specification:
cols(
  .default = col_character(),
  ZEIT = col_integer(),
  STADTRAUM = col_integer(),
  E_E = col_number(),
  E_EM = col_number(),
  E_EW = col_number(),
  E_E50_55 = col_number(),
  E_E25U55 = col_number(),
  E_E55U65 = col_number(),
  E_E65U80 = col_number()
)
See spec(...) for full column specifications.

Überprüfung der Vollständigkeit der Daten über Anmeldezahlen/Kapazitäten

re_schulstand_df = re_schulstand %>% as.data.frame() %>% rename(lon=coords.x1, lat=coords.x2) %>% cbind(over(re_schulstand, lor))
re_schulstand_df %>% filter(grepl('G', spatial_name)) %>% mutate(BEZIRK=enc2utf8(as.character(BEZIRK))) %>%
  group_by(BEZIRK) %>% summarise(`Anzahl Schulen` = n()) %>%
  rename(Bezirk=BEZIRK) %>% left_join(kapas %>% group_by(Bezirk) %>% summarise(`Mit Kapazität` = n()))
Joining, by = "Bezirk"

Für welche Bezirke haben wir für alle Schulen Kapazitäten gegeben?

re_schulstand %>% as.data.frame() %>% filter(grepl('G', spatial_name)) %>% mutate(BEZIRK=enc2utf8(as.character(BEZIRK))) %>% group_by(BEZIRK) %>% summarise(`Anzahl Schulen` = n()) %>%
  rename(Bezirk=BEZIRK) %>% left_join(kapas %>% group_by(Bezirk) %>% summarise(`Mit Kapazität` = n())) %>% filter(`Anzahl Schulen` == `Mit Kapazität`)
Joining, by = "Bezirk"

Überprüfung ob die Liste der Schulen und Liste der Schulen mit Kapazitätsinformationen gleich sind:

bezirk = 'Tempelhof-Schöneberg'
schulen_mit_kapa = kapas %>% filter(Bezirk == bezirk) %>% .$Schulnummer
schulen_mit_kapa
 [1] "07G01" "07G02" "07G03" "07G05" "07G06" "07G07" "07G10" "07G12"
 [9] "07G13" "07G14" "07G15" "07G16" "07G17" "07G18" "07G19" "07G20"
[17] "07G21" "07G22" "07G23" "07G24" "07G25" "07G26" "07G27" "07G28"
[25] "07G29" "07G30" "07G31" "07G32" "07G34" "07G35" "07G36" "07G37"
07G01

07G02

07G03

07G05

07G06

07G07

07G10

07G12

07G13

07G14

07G15

07G16

07G17

07G18

07G19

07G20

07G21

07G22

07G23

07G24

07G25

07G26

07G27

07G28

07G29

07G30

07G31

07G32

07G34

07G35

07G36

07G37
grundschulen = re_schulstand %>% as.data.frame() %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK == bezirk) %>% .$spatial_name
'In Anmeldeliste, fehlt in Schulstand'
[1] "In Anmeldeliste, fehlt in Schulstand"
In Anmeldeliste, fehlt in Schulstand
setdiff(schulen_mit_kapa, grundschulen)
character(0)
'In re_schulstand, fehlt in Anmeldeliste'
[1] "In re_schulstand, fehlt in Anmeldeliste"
In re_schulstand, fehlt in Anmeldeliste
setdiff(grundschulen, schulen_mit_kapa)
character(0)
map = get_map('Berlin')
Map from URL : http://maps.googleapis.com/maps/api/staticmap?center=Berlin&zoom=10&size=640x640&scale=2&maptype=terrain&language=en-EN&sensor=false
Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address=Berlin&sensor=false
re_schulstand_df_w_kapas = re_schulstand_df %>% left_join(kapas, by=c('spatial_name'='Schulnummer'))

Plot aller Schulen, mit der Info, ob Kapazitätsinformationen verfügbar sind.

data = re_schulstand_df_w_kapas %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK==bezirk) %>% mutate(missing.capa=is.na(`Plätze`))
ggmap(map) + geom_point(aes(lon, lat, color=missing.capa), data=data) +
    coord_map(xlim=c(min(data$lon)-0.01, max(data$lon)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Filter auf Schulen mit Kapazitätsinformationen (für T-S sind das alle):

relevant_schools = re_schulstand_df_w_kapas %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK==bezirk & !is.na(`Plätze`)) %>% .$spatial_name
relevant_schools
 [1] "07G01" "07G02" "07G03" "07G05" "07G06" "07G07" "07G10" "07G12"
 [9] "07G13" "07G14" "07G15" "07G16" "07G17" "07G18" "07G19" "07G20"
[17] "07G21" "07G22" "07G23" "07G24" "07G25" "07G26" "07G27" "07G28"
[25] "07G29" "07G30" "07G31" "07G32" "07G34" "07G35" "07G36" "07G37"
07G01

07G02

07G03

07G05

07G06

07G07

07G10

07G12

07G13

07G14

07G15

07G16

07G17

07G18

07G19

07G20

07G21

07G22

07G23

07G24

07G25

07G26

07G27

07G28

07G29

07G30

07G31

07G32

07G34

07G35

07G36

07G37

Mapping Bezirk->LOR->Block

df_bez = as.data.frame(bez)
df_lor = as.data.frame(lor)
df_blk = as.data.frame(blk)

Sanity-Check: LORs und Blöcke im Bezirk

bez_id = filter(df_bez, BEZNAME == bezirk)$BEZ
relevant_lors = df_lor %>% filter(BEZ == bez_id)
relevant_blks = df_blk %>% filter(BEZ == bez_id)
ggplot() + geom_path(aes(x=long, y=lat, group=group), data=lor[lor$BEZ == bez_id,]) + coord_map() + geom_path(aes(x=long, y=lat, group=group), data=bez, color='red')
Regions defined for each Polygons
Regions defined for each Polygons

Blöcke im Bezirk

ggplot() + geom_path(aes(x=long, y=lat, group=group), data=blk[blk$BEZ == bez_id,]) + coord_map() + geom_path(aes(x=long, y=lat, group=group), data=bez[bez$BEZ == bez_id,], color='red')
Regions defined for each Polygons
Regions defined for each Polygons

Kinder im Bezirk auf Blöcke hochrechnen

Über die Einwohnerinformationen in RBS_OD_BLK_2015_12.geojson kann EWR201512E_Matrix.csv von LOR-Ebene auf Blockebene hochgerechnet werden.

TODO: Stattdessen mit https://github.com/berlinermorgenpost/cogran machen?

Plot der 6-Jährigen nach EWR201512E_Matrix.csv

Wir verwenden das mittel der 5- und 6-Jährigen.

TODO neue Daten von Torres? TODO Prognose?

relevant_ewr = einwohner_lor %>%
  select(RAUMID, E_E05_06, E_E06_07) %>%
  filter(RAUMID %in% relevant_lors$PLR) %>%
  # Schnitt der 5 und 6-Jährigen
  mutate(kids=(as.numeric(gsub(',','.',E_E06_07))+as.numeric(gsub(',','.',E_E05_06)))/2) %>% as.data.frame()

data = tidy(lor[lor$BEZ == bez_id,], region='PLR') %>% inner_join(relevant_ewr, by=c('id'='RAUMID'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=kids), data=data) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Plot der Einwohner auf Blockebene

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(df_blk, by=c('id'='BLK')) %>% mutate(Einw=ifelse(Einw==0, NA, Einw))
0
[1] 0
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=Einw), data=data) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Hochrrechnung auf Blöcke, Strukturquote

Strukturquote = 0.9

kids_in_blks = relevant_blks %>%
  group_by(PLR) %>%
  mutate(EinwRatio = Einw/sum(Einw)) %>%
  ungroup %>%
  left_join(relevant_ewr, by=c('PLR'='RAUMID')) %>%
  mutate(kids = EinwRatio*kids) %>%
  mutate(kids = Strukturquote*kids) %>% # Strukturquote
  select(BEZ, PLR, BLK, Einw, kids) %>%
  as.data.frame()
row.names(kids_in_blks) = kids_in_blks$BLK

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(kids_in_blks, by=c('id'='BLK')) %>% mutate(kids=ifelse(kids==0, NA, kids), Einw=ifelse(Einw==0, NA, Einw))

ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=kids), data=data) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Verfügbare Plätze

relevant_kapas = kapas %>% select(Schulnummer, Kapa=`Plätze`) %>% filter(Schulnummer %in% relevant_schools) %>% as.data.frame()
#row.names(relevant_kapas) = relevant_kapas$Schulnummer

Überprüfung der Summe der Kapazitäten, Anmeldungen und Kinderstatistiken

'Summe Kapas'
[1] "Summe Kapas"
Summe Kapas
relevant_kapas %>% .$Kapa %>% sum
[1] 2584
'Anmeldungen'
[1] "Anmeldungen"
Anmeldungen
kapas %>% mutate(Anmeldungen = as.numeric(gsub('[^0-9]', '', Anmeldungen))) %>% filter(Schulnummer %in% relevant_schools) %>% .$Anmeldungen %>% sum
[1] 2752
'Kids laut Statistik'
[1] "Kids laut Statistik"
Kids laut Statistik
kids_in_blks$kids %>% sum
[1] 2620.8
relevant_ewr$kids %>% sum
[1] 2912

Schulwege von Blöcken zu Schulen aggregieren

Für jedes Wohngebäude suchen wir den zugehörigen Block

residential_buildings_blocks = sampled_buildings %>% inner_join(df_blk) %>% filter(BEZ == bez_id)
Joining, by = "BLK"
residential_buildings_blocks
routes_from_blks = residential_buildings_blocks %>%
  left_join(route_matrix %>% filter(dst %in% relevant_schools), by=c('OI'='src'))
head(routes_from_blks)

Plot der relevanten Blöcke (mit Wohngebäuden)

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(routes_from_blks %>% group_by(BLK) %>% summarise(n=n()), by=c('id'='BLK'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group), fill='red', data=data) +
  #geom_point(aes(x=lon, y=lat), data=rb_df, color='black', size=0.01) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

travel_from_blks = routes_from_blks %>% as.data.frame() %>% group_by(BLK, dst) %>% summarise(min=min(distance), avg=mean(distance), med=median(distance), max=max(distance)) %>% ungroup
travel_from_blks

Plot der Blöcke mit Färbung nach durchschnittlichem Weg zur nächsten Schule

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(travel_from_blks %>% group_by(BLK) %>% top_n(1, -avg), by=c('id'='BLK'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=-avg), data=data) +
  geom_point(aes(lon, lat), color='red', data = re_schulstand_df %>% filter(BEZIRK==bezirk & SCHULART=='Grundschule')) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

travel_matrix = travel_from_blks %>% select(BLK, dst, avg) %>% spread(dst, avg)
dim(travel_matrix)
[1] 1012   33
travel_matrix

Sozioökonomische Daten

Wir haben Sozioökonomische Daten in den Wahlbezirken. Strategie: - Schneiden der Wahlbezirke mit den Blöcken - Übernahme des Prozentwertes vom Wahlbezirk für jeden (Unter-)block - Zuordnung zu jedem Block und Vereinigung durch Flächen/Wohnhaus-gewichtetes Mittel des Prozentwertes

UWB = readOGR('download/RBS_OD_UWB_AGH2016.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_UWB_AGH2016.geojson", layer: "OGRGeoJSON"
with 1779 features
It has 4 fields
UWB$ID = paste0(UWB$BEZ, UWB$UWB)
sozio_UWB = read_excel('download/DL_BE_AH2016_Strukturdaten.xlsx', sheet = 3) %>% select(ID, sgbIIu65=`Einwohner unter 65 in SGB II 2014 Prozent`)
UWB = UWB %>% sp::merge(sozio_UWB, by='ID')

ggplot(broom::tidy(UWB, region='ID') %>% inner_join(UWB %>% as.data.frame, by=c('id'='ID'))) + geom_polygon(aes(x=long, y=lat, group=group, fill=sgbIIu65)) + coord_map()

Check for self intersections

wgs84 = CRS(proj4string(blk))
ea_projection = CRS("+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs")
blk_in_bez = blk[blk$BEZ == bez_id,]
gIsValid(blk_in_bez)
[1] TRUE
blk_in_bez = gBuffer(spTransform(blk_in_bez, ea_projection), byid=T, width = -0.1)
any(xor(gIntersects(blk_in_bez, byid=T, returnDense = T), diag(1, length(blk_in_bez)) == 1))
[1] FALSE
plot(blk_in_bez)

#gIntersects(UWB[UWB$BEZ == '07',], byid=TRUE)
uwb_in_bez = gBuffer(spTransform(UWB[UWB$BEZ == '07',], ea_projection), byid=T, width=-0.1)
gIsValid(uwb_in_bez)
[1] TRUE
any(xor(gIntersects(uwb_in_bez, byid=T, returnDense = T), diag(1, length(uwb_in_bez)) == 1))
[1] FALSE
plot(uwb_in_bez)

intersection = gIntersection(uwb_in_bez, blk_in_bez, byid = T, drop_lower_td = T)

gIsValid(intersection)
[1] TRUE
plot(intersection)

intersection_uwb_data = intersection %>% over(uwb_in_bez)
intersection_blk_data = intersection %>% over(blk_in_bez)
intersection_area = gArea(intersection, byid=T)
intersection_data = cbind(
    intersection_uwb_data %>% select(UWB_ID=ID, sgbIIu65),
    intersection_blk_data %>% select(BLK, BLK_Einw=Einw),
    data.frame(area=intersection_area) # FIXME how else to normalize?
    )
length(intersection)
[1] 1219
nrow(blk_in_bez)
[1] 1201
# did we miss any blocks?
setdiff(blk_in_bez$BLK, intersection_data$BLK)
character(0)

Pro Block mische die SGB-Werte der Unterblöcke gewichtet nach Fläche. FIXME - besser nach Anzahl der Wohnhäuser?

sgbII_blk = intersection_data %>%
  group_by(BLK) %>%
  summarise(sgbIIu65=sum(sgbIIu65*area)/sum(area)/100)
ggplot(broom::tidy(spTransform(blk_in_bez, wgs84), region='BLK') %>% left_join(sgbII_blk, by=c('id'='BLK'))) + geom_polygon(aes(x=long, y=lat, group=group, fill=sgbIIu65)) + coord_map()

Adjazenz von Blöcken

row.names(blk_in_bez) = blk_in_bez$BLK
buffeded_blk_in_bez = gBuffer(blk_in_bez, byid=T, width=40)
adjacency = gIntersects(gBuffer(blk_in_bez, byid=T, width=40), byid = T)
rownames(adjacency) = blk_in_bez$BLK
colnames(adjacency) = blk_in_bez$BLK

adjacency_df = adjacency %>% as.data.frame() %>% mutate(from=rownames(.)) %>%
  gather(to, connected, -from) %>% filter(connected) %>%
  inner_join(spTransform(blk_in_bez, wgs84) %>% coordinates() %>% as.data.frame() %>% rename(from_long=V1, from_lat=V2) %>% mutate(from=rownames(.))) %>%
  inner_join(spTransform(blk_in_bez, wgs84) %>% coordinates() %>% as.data.frame() %>% rename(to_long=V1, to_lat=V2) %>% mutate(to=rownames(.)))
Joining, by = "from"
Joining, by = "to"
ggplot() +
  geom_polygon(aes(x=long, y=lat, group=group), fill='gray', data=broom::tidy(spTransform(blk_in_bez, wgs84), region='BLK')) +
  geom_segment(aes(x=from_long, y=from_lat, xend=to_long, yend=to_lat), size=0.1, color='black', data=adjacency_df) +
  theme_nothing() + coord_map()
Warning: `panel.margin` is deprecated. Please use `panel.spacing` property
instead

ggsave('figs/adjacency.pdf')
Saving 7 x 5 in image
adjacency_df %>% filter(connected & from != to) %>% select(from, to) %>% write_csv('app/data/adjacency.csv')

Select relevant data

optim_kapas = relevant_kapas
optim_kids_in_blks = kids_in_blks %>% filter(kids > 0) %>% inner_join(travel_matrix, by='BLK') %>% select(BLK, kids) %>% mutate(kids=kids)
nrow(optim_kids_in_blks)
[1] 1012
nrow(optim_kapas)
[1] 32
select_schools = as.character(optim_kapas$Schulnummer)
select_blks = as.character(optim_kids_in_blks$BLK)

optim_matrix = inner_join(optim_kids_in_blks, travel_matrix, by='BLK')[select_schools]

dim(optim_matrix)
[1] 1012   32
optim_kapas$Kapa %>% sum
[1] 2584
optim_kids_in_blks$kids %>% sum
[1] 2611.995

Naive Zuordnung: Jeder Block zur nächsten Schule

solution = optim_matrix %>% mutate(BLK=optim_kids_in_blks$BLK) %>% gather(school, dist, -BLK) %>% group_by(BLK) %>% top_n(1, -dist) %>% ungroup

optim_matrix %>% t %>% as.data.frame %>% summarise_each(funs(min)) %>% sum()
[1] 780466.5
solines = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school')) %>% inner_join(cbind(as.data.frame(coordinates(blk[blk$BEZ == bez_id,])), blk[blk$BEZ == bez_id,]@data['BLK']))
Joining, by = "BLK"
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(solution, by=c('id'='BLK'))
ggmap(map, darken = c(0.5, 'white')) + geom_polygon(aes(x=long, y=lat, group=group, fill=school), data=data) +
  geom_segment(aes(x=V1,y=V2,xend=lon,yend=lat), data=solines, size=0.3) +
  geom_point(aes(lon, lat), color='black', size=2, data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
  geom_point(aes(lon, lat, color=spatial_name), data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01)) +
  guides(color=F, fill=F)

Darstellung der Zuordnung als Tabelle

library(formattable)
solution %>% inner_join(optim_kids_in_blks, by='BLK') %>% inner_join(travel_from_blks, by=c('BLK'='BLK', 'school'='dst')) %>%
  group_by(school) %>% summarise(
    kids=sum(kids),
    num_blocks=n(),
    min_dist=min(min),
    avg_dist=mean((kids*avg)/sum(kids)),
    max_dist=max(max)
  ) %>%
  inner_join(relevant_kapas, by=c('school'='Schulnummer')) %>%
  mutate(
    utilization=kids/Kapa
  ) %>% select(
   Schule=school,
   `Blöcke`=num_blocks,
   Kapazität=Kapa,
   Kinder=kids,
   Auslastung=utilization,
   `Weg (min)`=min_dist,
   `Weg (Ø)`=avg_dist,
   `Weg (max)`=max_dist
  ) %>%
  formattable(
    list(
      Kinder = formatter("span", x ~ digits(x, 2)),
      Auslastung = formatter("span",
        style = x ~ style(color = ifelse(x < 1, "green", "red")),
        x ~ icontext(ifelse(x < 1, "ok", "remove"), percent(x))
      ),
      `Weg (Ø)` = proportion_bar("lightblue"),
      `Weg (min)` = proportion_bar("lightblue"),
      `Weg (max)` = proportion_bar("lightblue")
    )
  )
Schule Blöcke Kapazität Kinder Auslastung Weg (min) Weg (Ø) Weg (max)
07G01 12 66 62.12 94.13% 224.845734 676.0297 1433.0243
07G02 20 98 47.57 48.54% 46.556602 422.7953 792.4623
07G03 11 73 47.23 64.70% 66.549644 430.1879 1106.3140
07G05 21 78 98.13 125.81% 56.170158 567.2555 965.2747
07G06 27 55 72.94 132.62% 236.302841 789.0100 1370.8586
07G07 12 81 20.70 25.56% 231.677170 711.5683 1572.3916
07G10 30 112 141.12 126.00% 47.832695 640.2891 1527.4636
07G12 13 75 35.51 47.34% 84.875656 354.5439 623.3419
07G13 27 82 130.38 159.00% 83.382767 494.2252 957.9329
07G14 33 78 80.83 103.63% 76.489960 422.2012 743.9302
07G15 53 104 218.74 210.32% 139.206421 835.4126 1650.3367
07G16 17 104 47.33 45.51% 135.925201 525.0121 912.3560
07G17 25 100 70.31 70.31% 51.462997 398.1707 710.3102
07G18 20 44 62.14 141.22% 97.120125 329.6189 720.3619
07G19 34 112 105.22 93.95% 60.016224 1117.3621 2279.4165
07G20 46 81 155.92 192.49% 98.167419 675.0707 1516.0098
07G21 21 72 55.20 76.67% 117.613678 487.9516 1200.9553
07G22 28 91 78.15 85.88% 123.256531 600.7208 1346.9137
07G23 10 50 23.72 47.44% 157.713531 678.1085 1193.4146
07G24 27 75 81.83 109.10% 67.979126 620.2428 1933.3115
07G25 35 75 112.70 150.26% 52.787018 622.3285 1279.9268
07G26 24 52 33.95 65.28% 77.638962 527.9525 1086.9845
07G27 17 75 42.11 56.15% 133.304367 650.2435 1366.7585
07G28 62 75 84.14 112.18% 137.133423 982.3003 1822.0323
07G29 95 104 115.72 111.27% 64.338501 940.4776 2020.5684
07G30 41 72 69.50 96.53% 82.527321 860.1026 2409.7322
07G31 20 78 47.49 60.88% 260.135132 883.8032 1464.1348
07G32 63 78 61.94 79.41% 42.085312 757.1401 1336.4922
07G34 40 100 176.64 176.64% 35.297066 1174.1653 2227.6230
07G35 35 75 84.89 113.18% 87.235146 771.1714 1455.3230
07G36 34 100 56.78 56.78% 192.735321 965.8743 2366.5962
07G37 59 69 91.06 131.97% 5.178658 1249.8005 2133.0967

Daten für die App speichern

  • entities.geojson
  • entities.csv
  • units.geojson
  • units.csv
  • weights.csv
  • assignment.csv

Neue Daten

Entities / Schulen

entities = subset(re_schulstand_df, spatial_name %in% relevant_schools) %>%
  rename(entity_id = spatial_name) %>%
  select(-gml_id, -spatial_alias, -spatial_type) %>%
  inner_join(rename(relevant_kapas, capacity=Kapa), by=c('entity_id'='Schulnummer'))
coordinates(entities) = ~ lon + lat
proj4string(entities) = CRS("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
file.remove('app/data/entities.geojson')
[1] TRUE
entities %>% writeOGR('app/data/entities.geojson', layer="entities", driver="GeoJSON", check_exists=F)
entities@data %>% select(entity_id, capacity) %>% write_csv('app/data/entities.csv')

Units / Statistische Blöcke

units = subset(blk, BEZ == bez_id)

units@data$PLR = NULL
units@data$Einw = NULL
units@data$BEZ = NULL
units@data$unit_id = units@data$BLK
units@data$BLK = NULL

units = units %>% sp::merge(
  optim_kids_in_blks %>%
    left_join(sgbII_blk) %>%
    select(unit_id=BLK, population=kids, sgbIIu65)
  )
Joining, by = "BLK"
file.remove('app/data/units.geojson')
[1] TRUE
units %>% writeOGR('app/data/units.geojson', layer="units", driver="GeoJSON", check_exists=F)
units@data %>% write_csv('app/data/units.csv')

Zuordnung

assignment = solution %>% select(unit_id=BLK, entity_id=school)
assignment %>% write_csv('app/data/assignment.csv')

Weights / Schulwege

travel_from_blks %>%
  rename(unit_id=BLK, entity_id=dst) %>%
  #gather(weight, value, -unit_id, -entity_id) %>%
  write_csv('app/data/weights.csv')

Zusätzliche Daten

file.copy('download/RBS_OD_BEZ_2015_12.geojson', 'app/data/RBS_OD_BEZ_2015_12.geojson')
[1] FALSE
addresses_in_bez = HKO_2015[!is.na(over(HKO_2015, subset(blk, BEZ == bez_id))$BEZ),c('STN', 'HNR')]
names(addresses_in_bez) = c('street', 'no')
addresses_in_bez@data
file.remove('app/data/addresses.geojson')
[1] TRUE
writeOGR(addresses_in_bez, "app/data/addresses.geojson", layer="addresses", driver="GeoJSON", check_exists = FALSE)
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IFRSVUUKICAgIHRvY19mbG9hdDogVFJVRQotLS0KCmBgYHtyIGxpYnMsIGluY2x1ZGU9Riwgd2FybmluZz1GfQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KHJlYWR4bCkKbGlicmFyeShyZ2RhbCkKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGdnbWFwKQpsaWJyYXJ5KHB1cnJyKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KGJyb29tKQpsaWJyYXJ5KG1hcHRvb2xzKQpsaWJyYXJ5KHJnZW9zKQpgYGAKCkRpZXNlcyBOb3RlYm9vayBiZXJlaXRldCBkaWUgRGF0ZW4gZsO8ciBkaWUgSW50ZWxsaWdlbnQgWm9uaW5nIEVuZ2luZSB2b3IuIEVzIHNwZWljaGVydAoKLSBlbnRpdGllcy5nZW9qc29uIOKAlCBTY2h1bGVuLCBkZXJlbiBHZW9rb29yZGluYXRlbiB1bmQgQXR0cmlidXRlOiBlbnRpdHlfaWQsIGNhcGFjaXR5LCB1bmQgYW5kZXJlIEF0dHJpYnV0ZQotIGVudGl0aWVzLmNzdiDigJQgU2NodWxlbiB1bmQgb3B0aW1pZXJ1bmdzcmVsZXZhbnRlIEF0dHJpYnV0ZTogZW50aXR5X2lkLCBjYXBhY2l0eQotIHVuaXRzLmdlb2pzb24g4oCUIFN0YXRpc3Rpc2NoZSBCbMO2Y2tlIEJlcmxpbnMsIGRlcmVuIEdlb21ldHJpZSB1bmQgQXR0cmlidXRlCi0gdW5pdHMuY3N2IOKAlCBTdGF0c2l0aXNjaGUgQmzDtmNrZSBCZXJsaW5zIHVuZCBvcHRpbWllcnVuZ3NyZWxldmFudGUgQXR0cmlidXRlOiB1bml0X2lkLCBwb3B1bGF0aW9uLCBwZXJjZW50YWdlX3NnYgotIHdlaWdodHMuY3N2IOKAlCBvcHRpbWllcnVuZ3NyZWxldmFudGUgR2V3aWNodGUgd2llIEZ1w593ZWdlIC8gU3BhbHRlbjogZW50aXR5X2lkLCB1bml0X2lkLCB3ZWlnaHQsIHZhbHVlCi0gYXNzaWdubWVudC5jc3Yg4oCUIGVpbmUgaW5pdGlhbGUgWnVvcmRudW5nIC8gU3BhbHRlbjogdW5pdF9pZCwgZW50aXR5X2lkCgpCZXJlaXRzIGluIGFuZGVyZW4gc2NyaXB0ZW4gd3VyZGUgdm9yYmVyZWl0ZXQ6CgotIEZ1w593ZWdlbiB2b24gZWluZXIgZ3Jvw59lbiBTaWNocHJvYmUgdm9uIChfV29obl8tKUdlYsOkdWRlbiB6dSBhbGxlbiBTY2h1bGVuIHd1cmRlbiBiZXJlY2huZXQgdW5kIGluIGByb3V0ZV9tYXRyaXguY3N2YCBnZXNwZWljaGVydAotIERpZSBTdGljaHByb2JlIHd1cmRlIGluIGBzYW1wbGVkX2J1aWxkaW5ncy5jc3ZgIGdlc3BlaWNoZXJ0CgpEaWUgRGF0ZW4gd2VyZGVuIHdpZWZvbGd0IHZvcmJlcmVpdGV0OgoKLSBwcm8gQmxvY2sgd2VyZGVuIGRpZSBBbnphaGwgZGVyIGVpbnp1c2NodWxlbmRlbiBLaW5kZXIgbWl0IEhpbGZlIGRlciBFaW53b2huZXJ6YWhsZW4gbmFjaCBBbHRlciBhdWYgTE9SLUViZW5lIGluIGBFV1IyMDE1MTJFX01hdHJpeC5jc3ZgIGhvY2hnZXJlY2huZXQKICAgIC0gS2luZGVyIGRlcyBMT1Igd2VyZGVuIEFudGVpbGlnIG5hY2ggRWlud29obmVyemFobCBkZXMgQmxvY2tzIGltIFZlcmjDpGx0bmlzIHp1bSBMT1IgYXVmIGRpZSBCbMO2Y2tlIHZlcnRlaWx0Ci0gZXMgd2VyZGVuIG1pbmltYWxlLCBkdXJjaHNjaG5pdHRsaWNoZSB1bmQgbWF4aW1hbGUgRnXDn3dlZ2UgYXVzIGplZGVtIEJsb2NrIGVycmVjaG5ldAoKVE9ETzoKLSBkaWUgc296aW/Dtmtvbm9taXNjaGVuIEZha3RvcmVuIHdlcmRlbiBhdXMgZGVuIFdhaGxiZXppcmtlbiBhdWYgZGllIEJsw7Zja2UgaG9jaGdlcmVjaG5ldCAoaHR0cHM6Ly9naXRodWIuY29tL2JlcmxpbmVybW9yZ2VucG9zdC9jb2dyYW4pCgojIyBMYWRlbiBkZXIgRGF0ZW4KCmBgYHtyIGxvYWQgZGF0YSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kc2FtcGxlZF9idWlsZGluZ3MgPSByZWFkX3Jkcygnb3V0cHV0L3NhbXBsZWRfYnVpbGRpbmdzLnJkcycpCmJleiA9IHJlYWRPR1IoJ2Rvd25sb2FkL1JCU19PRF9CRVpfMjAxNV8xMi5nZW9qc29uJywgbGF5ZXIgPSAnT0dSR2VvSlNPTicsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKYmxrID0gcmVhZE9HUignZG93bmxvYWQvUkJTX09EX0JMS18yMDE1XzEyLmdlb2pzb24nLCBsYXllciA9ICdPR1JHZW9KU09OJywgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpsb3IgPSByZWFkT0dSKCdkb3dubG9hZC9SQlNfT0RfTE9SXzIwMTVfMTIuZ2VvanNvbicsIGxheWVyID0gJ09HUkdlb0pTT04nLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCnJlX3NjaHVsc3RhbmQgPSByZWFkT0dSKCdkb3dubG9hZC9yZV9zY2h1bHN0YW5kLmdlb2pzb24nLCBsYXllciA9ICdPR1JHZW9KU09OJywgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpIS09fMjAxNSA9IHJlYWRPR1IoJ291dHB1dC9IS09fMjAxNS5nZW9qc29uJywgJ09HUkdlb0pTT04nLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmBgYAoKIyMjIFNjaHVsd2VnZQoKYGBge3J9CnJvdXRlX21hdHJpeCA9IHJlYWRfcmRzKCdvdXRwdXQvcm91dGVfbWF0cml4LnJkcycpCmBgYAoKIyMjIFNjaHVsa2FwYXppdMOkdGVuIHVuZCBFaW53b2huZXJ6YWhsZXIgYXVmIExPUi1FYmVuZQoKYGBge3J9CmthcGFzID0gcmVhZF9jc3YoJ2Rvd25sb2FkL2FubWVsZGV6YWhsZW4uY3N2JykgJT4lIGZpbHRlcihncmVwbCgnRycsIFNjaHVsbnVtbWVyKSkgJT4lIGZpbHRlcighaXMubmEoYFBsw6R0emVgKSkKZWlud29obmVyX2xvciA9IHJlYWRfZGVsaW0oJ2Rvd25sb2FkL0VXUjIwMTUxMkVfTWF0cml4LmNzdicsIGRlbGltPSc7JykKYGBgCgojIyDDnGJlcnByw7xmdW5nIGRlciBWb2xsc3TDpG5kaWdrZWl0IGRlciBEYXRlbiDDvGJlciBBbm1lbGRlemFobGVuL0thcGF6aXTDpHRlbgoKYGBge3J9CnJlX3NjaHVsc3RhbmRfZGYgPSByZV9zY2h1bHN0YW5kICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHJlbmFtZShsb249Y29vcmRzLngxLCBsYXQ9Y29vcmRzLngyKSAlPiUgY2JpbmQob3ZlcihyZV9zY2h1bHN0YW5kLCBsb3IpKQpyZV9zY2h1bHN0YW5kX2RmICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBzcGF0aWFsX25hbWUpKSAlPiUgbXV0YXRlKEJFWklSSz1lbmMydXRmOChhcy5jaGFyYWN0ZXIoQkVaSVJLKSkpICU+JQogIGdyb3VwX2J5KEJFWklSSykgJT4lIHN1bW1hcmlzZShgQW56YWhsIFNjaHVsZW5gID0gbigpKSAlPiUKICByZW5hbWUoQmV6aXJrPUJFWklSSykgJT4lIGxlZnRfam9pbihrYXBhcyAlPiUgZ3JvdXBfYnkoQmV6aXJrKSAlPiUgc3VtbWFyaXNlKGBNaXQgS2FwYXppdMOkdGAgPSBuKCkpKQpgYGAKCkbDvHIgd2VsY2hlIEJlemlya2UgaGFiZW4gd2lyIGbDvHIgYWxsZSBTY2h1bGVuIEthcGF6aXTDpHRlbiBnZWdlYmVuPwoKYGBge3J9CnJlX3NjaHVsc3RhbmQgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZmlsdGVyKGdyZXBsKCdHJywgc3BhdGlhbF9uYW1lKSkgJT4lIG11dGF0ZShCRVpJUks9ZW5jMnV0ZjgoYXMuY2hhcmFjdGVyKEJFWklSSykpKSAlPiUgZ3JvdXBfYnkoQkVaSVJLKSAlPiUgc3VtbWFyaXNlKGBBbnphaGwgU2NodWxlbmAgPSBuKCkpICU+JQogIHJlbmFtZShCZXppcms9QkVaSVJLKSAlPiUgbGVmdF9qb2luKGthcGFzICU+JSBncm91cF9ieShCZXppcmspICU+JSBzdW1tYXJpc2UoYE1pdCBLYXBheml0w6R0YCA9IG4oKSkpICU+JSBmaWx0ZXIoYEFuemFobCBTY2h1bGVuYCA9PSBgTWl0IEthcGF6aXTDpHRgKQpgYGAKCsOcYmVycHLDvGZ1bmcgb2IgZGllIExpc3RlIGRlciBTY2h1bGVuIHVuZCBMaXN0ZSBkZXIgU2NodWxlbiBtaXQgS2FwYXppdMOkdHNpbmZvcm1hdGlvbmVuIGdsZWljaCBzaW5kOgoKYGBge3J9CmJlemlyayA9ICdUZW1wZWxob2YtU2Now7ZuZWJlcmcnCnNjaHVsZW5fbWl0X2thcGEgPSBrYXBhcyAlPiUgZmlsdGVyKEJlemlyayA9PSBiZXppcmspICU+JSAuJFNjaHVsbnVtbWVyCnNjaHVsZW5fbWl0X2thcGEKZ3J1bmRzY2h1bGVuID0gcmVfc2NodWxzdGFuZCAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBzcGF0aWFsX25hbWUpKSAlPiUgZmlsdGVyKEJFWklSSyA9PSBiZXppcmspICU+JSAuJHNwYXRpYWxfbmFtZQonSW4gQW5tZWxkZWxpc3RlLCBmZWhsdCBpbiBTY2h1bHN0YW5kJwpzZXRkaWZmKHNjaHVsZW5fbWl0X2thcGEsIGdydW5kc2NodWxlbikKJ0luIHJlX3NjaHVsc3RhbmQsIGZlaGx0IGluIEFubWVsZGVsaXN0ZScKc2V0ZGlmZihncnVuZHNjaHVsZW4sIHNjaHVsZW5fbWl0X2thcGEpCmBgYAoKCmBgYHtyfQptYXAgPSBnZXRfbWFwKCdCZXJsaW4nKQpgYGAKCgpgYGB7cn0KcmVfc2NodWxzdGFuZF9kZl93X2thcGFzID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgbGVmdF9qb2luKGthcGFzLCBieT1jKCdzcGF0aWFsX25hbWUnPSdTY2h1bG51bW1lcicpKQpgYGAKClBsb3QgYWxsZXIgU2NodWxlbiwgbWl0IGRlciBJbmZvLCBvYiBLYXBheml0w6R0c2luZm9ybWF0aW9uZW4gdmVyZsO8Z2JhciBzaW5kLgoKYGBge3J9CmRhdGEgPSByZV9zY2h1bHN0YW5kX2RmX3dfa2FwYXMgJT4lIGZpbHRlcihncmVwbCgnRycsIHNwYXRpYWxfbmFtZSkpICU+JSBmaWx0ZXIoQkVaSVJLPT1iZXppcmspICU+JSBtdXRhdGUobWlzc2luZy5jYXBhPWlzLm5hKGBQbMOkdHplYCkpCmdnbWFwKG1hcCkgKyBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCwgY29sb3I9bWlzc2luZy5jYXBhKSwgZGF0YT1kYXRhKSArCiAgICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbiktMC4wMSwgbWF4KGRhdGEkbG9uKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgpGaWx0ZXIgYXVmIFNjaHVsZW4gbWl0IEthcGF6aXTDpHRzaW5mb3JtYXRpb25lbiAoZsO8ciBULVMgc2luZCBkYXMgYWxsZSk6CgpgYGB7cn0KcmVsZXZhbnRfc2Nob29scyA9IHJlX3NjaHVsc3RhbmRfZGZfd19rYXBhcyAlPiUgZmlsdGVyKGdyZXBsKCdHJywgc3BhdGlhbF9uYW1lKSkgJT4lIGZpbHRlcihCRVpJUks9PWJlemlyayAmICFpcy5uYShgUGzDpHR6ZWApKSAlPiUgLiRzcGF0aWFsX25hbWUKcmVsZXZhbnRfc2Nob29scwpgYGAKCiMjIE1hcHBpbmcgQmV6aXJrLT5MT1ItPkJsb2NrCgpgYGB7cn0KZGZfYmV6ID0gYXMuZGF0YS5mcmFtZShiZXopCmRmX2xvciA9IGFzLmRhdGEuZnJhbWUobG9yKQpkZl9ibGsgPSBhcy5kYXRhLmZyYW1lKGJsaykKYGBgCgojIyMgU2FuaXR5LUNoZWNrOiBMT1JzIHVuZCBCbMO2Y2tlIGltIEJlemlyawoKYGBge3J9CmJlel9pZCA9IGZpbHRlcihkZl9iZXosIEJFWk5BTUUgPT0gYmV6aXJrKSRCRVoKcmVsZXZhbnRfbG9ycyA9IGRmX2xvciAlPiUgZmlsdGVyKEJFWiA9PSBiZXpfaWQpCnJlbGV2YW50X2Jsa3MgPSBkZl9ibGsgJT4lIGZpbHRlcihCRVogPT0gYmV6X2lkKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoKSArIGdlb21fcGF0aChhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBkYXRhPWxvcltsb3IkQkVaID09IGJlel9pZCxdKSArIGNvb3JkX21hcCgpICsgZ2VvbV9wYXRoKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGRhdGE9YmV6LCBjb2xvcj0ncmVkJykKYGBgCgojIyMgQmzDtmNrZSBpbSBCZXppcmsKCmBgYHtyfQpnZ3Bsb3QoKSArIGdlb21fcGF0aChhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBkYXRhPWJsa1tibGskQkVaID09IGJlel9pZCxdKSArIGNvb3JkX21hcCgpICsgZ2VvbV9wYXRoKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGRhdGE9YmV6W2JleiRCRVogPT0gYmV6X2lkLF0sIGNvbG9yPSdyZWQnKQpgYGAKCiMjIEtpbmRlciBpbSBCZXppcmsgYXVmIEJsw7Zja2UgaG9jaHJlY2huZW4KCsOcYmVyIGRpZSBFaW53b2huZXJpbmZvcm1hdGlvbmVuIGluIGBSQlNfT0RfQkxLXzIwMTVfMTIuZ2VvanNvbmAga2FubiBgRVdSMjAxNTEyRV9NYXRyaXguY3N2YCB2b24gTE9SLUViZW5lIGF1ZiBCbG9ja2ViZW5lIGhvY2hnZXJlY2huZXQgd2VyZGVuLgoKVE9ETzogU3RhdHRkZXNzZW4gbWl0IGh0dHBzOi8vZ2l0aHViLmNvbS9iZXJsaW5lcm1vcmdlbnBvc3QvY29ncmFuIG1hY2hlbj8KCiMjIyBQbG90IGRlciA2LUrDpGhyaWdlbiBuYWNoIGBFV1IyMDE1MTJFX01hdHJpeC5jc3ZgCgpXaXIgdmVyd2VuZGVuIGRhcyBtaXR0ZWwgZGVyIDUtIHVuZCA2LUrDpGhyaWdlbi4KClRPRE8gbmV1ZSBEYXRlbiB2b24gVG9ycmVzPwpUT0RPIFByb2dub3NlPwoKYGBge3J9CnJlbGV2YW50X2V3ciA9IGVpbndvaG5lcl9sb3IgJT4lCiAgc2VsZWN0KFJBVU1JRCwgRV9FMDVfMDYsIEVfRTA2XzA3KSAlPiUKICBmaWx0ZXIoUkFVTUlEICVpbiUgcmVsZXZhbnRfbG9ycyRQTFIpICU+JQogICMgU2Nobml0dCBkZXIgNSB1bmQgNi1Kw6RocmlnZW4KICBtdXRhdGUoa2lkcz0oYXMubnVtZXJpYyhnc3ViKCcsJywnLicsRV9FMDZfMDcpKSthcy5udW1lcmljKGdzdWIoJywnLCcuJyxFX0UwNV8wNikpKS8yKSAlPiUgYXMuZGF0YS5mcmFtZSgpCgpkYXRhID0gdGlkeShsb3JbbG9yJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdQTFInKSAlPiUgaW5uZXJfam9pbihyZWxldmFudF9ld3IsIGJ5PWMoJ2lkJz0nUkFVTUlEJykpCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPWtpZHMpLCBkYXRhPWRhdGEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCiMjIyBQbG90IGRlciBFaW53b2huZXIgYXVmIEJsb2NrZWJlbmUKCmBgYHtyfQpkYXRhID0gdGlkeShibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdCTEsnKSAlPiUgaW5uZXJfam9pbihkZl9ibGssIGJ5PWMoJ2lkJz0nQkxLJykpICU+JSBtdXRhdGUoRWludz1pZmVsc2UoRWludz09MCwgTkEsIEVpbncpKQowCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPUVpbncpLCBkYXRhPWRhdGEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCgojIyMgSG9jaHJyZWNobnVuZyBhdWYgQmzDtmNrZSwgU3RydWt0dXJxdW90ZQoKYGBge3J9ClN0cnVrdHVycXVvdGUgPSAwLjkKCmtpZHNfaW5fYmxrcyA9IHJlbGV2YW50X2Jsa3MgJT4lCiAgZ3JvdXBfYnkoUExSKSAlPiUKICBtdXRhdGUoRWlud1JhdGlvID0gRWludy9zdW0oRWludykpICU+JQogIHVuZ3JvdXAgJT4lCiAgbGVmdF9qb2luKHJlbGV2YW50X2V3ciwgYnk9YygnUExSJz0nUkFVTUlEJykpICU+JQogIG11dGF0ZShraWRzID0gRWlud1JhdGlvKmtpZHMpICU+JQogIG11dGF0ZShraWRzID0gU3RydWt0dXJxdW90ZSpraWRzKSAlPiUgIyBTdHJ1a3R1cnF1b3RlCiAgc2VsZWN0KEJFWiwgUExSLCBCTEssIEVpbncsIGtpZHMpICU+JQogIGFzLmRhdGEuZnJhbWUoKQpyb3cubmFtZXMoa2lkc19pbl9ibGtzKSA9IGtpZHNfaW5fYmxrcyRCTEsKCmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBpbm5lcl9qb2luKGtpZHNfaW5fYmxrcywgYnk9YygnaWQnPSdCTEsnKSkgJT4lIG11dGF0ZShraWRzPWlmZWxzZShraWRzPT0wLCBOQSwga2lkcyksIEVpbnc9aWZlbHNlKEVpbnc9PTAsIE5BLCBFaW53KSkKCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPWtpZHMpLCBkYXRhPWRhdGEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCiMjIFZlcmbDvGdiYXJlIFBsw6R0emUKCmBgYHtyfQpyZWxldmFudF9rYXBhcyA9IGthcGFzICU+JSBzZWxlY3QoU2NodWxudW1tZXIsIEthcGE9YFBsw6R0emVgKSAlPiUgZmlsdGVyKFNjaHVsbnVtbWVyICVpbiUgcmVsZXZhbnRfc2Nob29scykgJT4lIGFzLmRhdGEuZnJhbWUoKQojcm93Lm5hbWVzKHJlbGV2YW50X2thcGFzKSA9IHJlbGV2YW50X2thcGFzJFNjaHVsbnVtbWVyCmBgYAoKIyMjIMOcYmVycHLDvGZ1bmcgZGVyIFN1bW1lIGRlciBLYXBheml0w6R0ZW4sIEFubWVsZHVuZ2VuIHVuZCBLaW5kZXJzdGF0aXN0aWtlbgoKYGBge3J9CidTdW1tZSBLYXBhcycKcmVsZXZhbnRfa2FwYXMgJT4lIC4kS2FwYSAlPiUgc3VtCidBbm1lbGR1bmdlbicKa2FwYXMgJT4lIG11dGF0ZShBbm1lbGR1bmdlbiA9IGFzLm51bWVyaWMoZ3N1YignW14wLTldJywgJycsIEFubWVsZHVuZ2VuKSkpICU+JSBmaWx0ZXIoU2NodWxudW1tZXIgJWluJSByZWxldmFudF9zY2hvb2xzKSAlPiUgLiRBbm1lbGR1bmdlbiAlPiUgc3VtCidLaWRzIGxhdXQgU3RhdGlzdGlrJwpraWRzX2luX2Jsa3Mka2lkcyAlPiUgc3VtCnJlbGV2YW50X2V3ciRraWRzICU+JSBzdW0KYGBgCgojIyBTY2h1bHdlZ2Ugdm9uIEJsw7Zja2VuIHp1IFNjaHVsZW4gYWdncmVnaWVyZW4KCkbDvHIgamVkZXMgV29obmdlYsOkdWRlIHN1Y2hlbiB3aXIgZGVuIHp1Z2Vow7ZyaWdlbiBCbG9jawoKYGBge3J9CnJlc2lkZW50aWFsX2J1aWxkaW5nc19ibG9ja3MgPSBzYW1wbGVkX2J1aWxkaW5ncyAlPiUgaW5uZXJfam9pbihkZl9ibGspICU+JSBmaWx0ZXIoQkVaID09IGJlel9pZCkKcmVzaWRlbnRpYWxfYnVpbGRpbmdzX2Jsb2NrcwpgYGAKCmBgYHtyfQpyb3V0ZXNfZnJvbV9ibGtzID0gcmVzaWRlbnRpYWxfYnVpbGRpbmdzX2Jsb2NrcyAlPiUKICBsZWZ0X2pvaW4ocm91dGVfbWF0cml4ICU+JSBmaWx0ZXIoZHN0ICVpbiUgcmVsZXZhbnRfc2Nob29scyksIGJ5PWMoJ09JJz0nc3JjJykpCmhlYWQocm91dGVzX2Zyb21fYmxrcykKYGBgCgojIyMgUGxvdCBkZXIgcmVsZXZhbnRlbiBCbMO2Y2tlIChtaXQgV29obmdlYsOkdWRlbikKCmBgYHtyfQpkYXRhID0gdGlkeShibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdCTEsnKSAlPiUgaW5uZXJfam9pbihyb3V0ZXNfZnJvbV9ibGtzICU+JSBncm91cF9ieShCTEspICU+JSBzdW1tYXJpc2Uobj1uKCkpLCBieT1jKCdpZCc9J0JMSycpKQpnZ21hcChtYXApICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGZpbGw9J3JlZCcsIGRhdGE9ZGF0YSkgKwogICNnZW9tX3BvaW50KGFlcyh4PWxvbiwgeT1sYXQpLCBkYXRhPXJiX2RmLCBjb2xvcj0nYmxhY2snLCBzaXplPTAuMDEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCgpgYGB7cn0KdHJhdmVsX2Zyb21fYmxrcyA9IHJvdXRlc19mcm9tX2Jsa3MgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZ3JvdXBfYnkoQkxLLCBkc3QpICU+JSBzdW1tYXJpc2UobWluPW1pbihkaXN0YW5jZSksIGF2Zz1tZWFuKGRpc3RhbmNlKSwgbWVkPW1lZGlhbihkaXN0YW5jZSksIG1heD1tYXgoZGlzdGFuY2UpKSAlPiUgdW5ncm91cAp0cmF2ZWxfZnJvbV9ibGtzCmBgYAoKIyMjIFBsb3QgZGVyIEJsw7Zja2UgbWl0IEbDpHJidW5nIG5hY2ggZHVyY2hzY2huaXR0bGljaGVtIFdlZyB6dXIgbsOkY2hzdGVuIFNjaHVsZQoKYGBge3J9CmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBsZWZ0X2pvaW4odHJhdmVsX2Zyb21fYmxrcyAlPiUgZ3JvdXBfYnkoQkxLKSAlPiUgdG9wX24oMSwgLWF2ZyksIGJ5PWMoJ2lkJz0nQkxLJykpCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPS1hdmcpLCBkYXRhPWRhdGEpICsKICBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCksIGNvbG9yPSdyZWQnLCBkYXRhID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgZmlsdGVyKEJFWklSSz09YmV6aXJrICYgU0NIVUxBUlQ9PSdHcnVuZHNjaHVsZScpKSArCiAgY29vcmRfbWFwKHhsaW09YyhtaW4oZGF0YSRsb25nKS0wLjAxLCBtYXgoZGF0YSRsb25nKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgoKYGBge3J9CnRyYXZlbF9tYXRyaXggPSB0cmF2ZWxfZnJvbV9ibGtzICU+JSBzZWxlY3QoQkxLLCBkc3QsIGF2ZykgJT4lIHNwcmVhZChkc3QsIGF2ZykKZGltKHRyYXZlbF9tYXRyaXgpCnRyYXZlbF9tYXRyaXgKYGBgCgojIyBTb3ppb8O2a29ub21pc2NoZSBEYXRlbgoKV2lyIGhhYmVuIFNvemlvw7Zrb25vbWlzY2hlIERhdGVuIGluIGRlbiBXYWhsYmV6aXJrZW4uIFN0cmF0ZWdpZToKLSBTY2huZWlkZW4gZGVyIFdhaGxiZXppcmtlIG1pdCBkZW4gQmzDtmNrZW4KLSDDnGJlcm5haG1lIGRlcyBQcm96ZW50d2VydGVzIHZvbSBXYWhsYmV6aXJrIGbDvHIgamVkZW4gKFVudGVyLSlibG9jawotIFp1b3JkbnVuZyB6dSBqZWRlbSBCbG9jayB1bmQgVmVyZWluaWd1bmcgZHVyY2ggRmzDpGNoZW4vV29obmhhdXMtZ2V3aWNodGV0ZXMgTWl0dGVsIGRlcyBQcm96ZW50d2VydGVzCgpgYGB7cn0KVVdCID0gcmVhZE9HUignZG93bmxvYWQvUkJTX09EX1VXQl9BR0gyMDE2Lmdlb2pzb24nLCBsYXllciA9ICdPR1JHZW9KU09OJywgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpVV0IkSUQgPSBwYXN0ZTAoVVdCJEJFWiwgVVdCJFVXQikKc296aW9fVVdCID0gcmVhZF9leGNlbCgnZG93bmxvYWQvRExfQkVfQUgyMDE2X1N0cnVrdHVyZGF0ZW4ueGxzeCcsIHNoZWV0ID0gMykgJT4lIHNlbGVjdChJRCwgc2diSUl1NjU9YEVpbndvaG5lciB1bnRlciA2NSBpbiBTR0IgSUkgMjAxNCBQcm96ZW50YCkKVVdCID0gVVdCICU+JSBzcDo6bWVyZ2Uoc296aW9fVVdCLCBieT0nSUQnKQoKZ2dwbG90KGJyb29tOjp0aWR5KFVXQiwgcmVnaW9uPSdJRCcpICU+JSBpbm5lcl9qb2luKFVXQiAlPiUgYXMuZGF0YS5mcmFtZSwgYnk9YygnaWQnPSdJRCcpKSkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPXNnYklJdTY1KSkgKyBjb29yZF9tYXAoKQpgYGAKCgpDaGVjayBmb3Igc2VsZiBpbnRlcnNlY3Rpb25zCmBgYHtyfQp3Z3M4NCA9IENSUyhwcm9qNHN0cmluZyhibGspKQplYV9wcm9qZWN0aW9uID0gQ1JTKCIrcHJvaj1sYWVhICtsYXRfMD01MiArbG9uXzA9MTAgK3hfMD00MzIxMDAwICt5XzA9MzIxMDAwMCArZWxscHM9R1JTODAgK3Rvd2dzODQ9MCwwLDAsMCwwLDAsMCArdW5pdHM9bSArbm9fZGVmcyIpCmJsa19pbl9iZXogPSBibGtbYmxrJEJFWiA9PSBiZXpfaWQsXQpnSXNWYWxpZChibGtfaW5fYmV6KQpibGtfaW5fYmV6ID0gZ0J1ZmZlcihzcFRyYW5zZm9ybShibGtfaW5fYmV6LCBlYV9wcm9qZWN0aW9uKSwgYnlpZD1ULCB3aWR0aCA9IC0wLjEpCmFueSh4b3IoZ0ludGVyc2VjdHMoYmxrX2luX2JleiwgYnlpZD1ULCByZXR1cm5EZW5zZSA9IFQpLCBkaWFnKDEsIGxlbmd0aChibGtfaW5fYmV6KSkgPT0gMSkpCnBsb3QoYmxrX2luX2JleikKI2dJbnRlcnNlY3RzKFVXQltVV0IkQkVaID09ICcwNycsXSwgYnlpZD1UUlVFKQpgYGAKCmBgYHtyfQp1d2JfaW5fYmV6ID0gZ0J1ZmZlcihzcFRyYW5zZm9ybShVV0JbVVdCJEJFWiA9PSAnMDcnLF0sIGVhX3Byb2plY3Rpb24pLCBieWlkPVQsIHdpZHRoPS0wLjEpCmdJc1ZhbGlkKHV3Yl9pbl9iZXopCmFueSh4b3IoZ0ludGVyc2VjdHModXdiX2luX2JleiwgYnlpZD1ULCByZXR1cm5EZW5zZSA9IFQpLCBkaWFnKDEsIGxlbmd0aCh1d2JfaW5fYmV6KSkgPT0gMSkpCnBsb3QodXdiX2luX2JleikKYGBgCgpgYGB7cn0KaW50ZXJzZWN0aW9uID0gZ0ludGVyc2VjdGlvbih1d2JfaW5fYmV6LCBibGtfaW5fYmV6LCBieWlkID0gVCwgZHJvcF9sb3dlcl90ZCA9IFQpCgpnSXNWYWxpZChpbnRlcnNlY3Rpb24pCnBsb3QoaW50ZXJzZWN0aW9uKQpgYGAKCmBgYHtyfQppbnRlcnNlY3Rpb25fdXdiX2RhdGEgPSBpbnRlcnNlY3Rpb24gJT4lIG92ZXIodXdiX2luX2JleikKaW50ZXJzZWN0aW9uX2Jsa19kYXRhID0gaW50ZXJzZWN0aW9uICU+JSBvdmVyKGJsa19pbl9iZXopCmludGVyc2VjdGlvbl9hcmVhID0gZ0FyZWEoaW50ZXJzZWN0aW9uLCBieWlkPVQpCmludGVyc2VjdGlvbl9kYXRhID0gY2JpbmQoCiAgICBpbnRlcnNlY3Rpb25fdXdiX2RhdGEgJT4lIHNlbGVjdChVV0JfSUQ9SUQsIHNnYklJdTY1KSwKICAgIGludGVyc2VjdGlvbl9ibGtfZGF0YSAlPiUgc2VsZWN0KEJMSywgQkxLX0Vpbnc9RWludyksCiAgICBkYXRhLmZyYW1lKGFyZWE9aW50ZXJzZWN0aW9uX2FyZWEpICMgRklYTUUgaG93IGVsc2UgdG8gbm9ybWFsaXplPwogICAgKQpgYGAKCmBgYHtyfQpsZW5ndGgoaW50ZXJzZWN0aW9uKQpucm93KGJsa19pbl9iZXopCiMgZGlkIHdlIG1pc3MgYW55IGJsb2Nrcz8Kc2V0ZGlmZihibGtfaW5fYmV6JEJMSywgaW50ZXJzZWN0aW9uX2RhdGEkQkxLKQpgYGAKClBybyBCbG9jayBtaXNjaGUgZGllIFNHQi1XZXJ0ZSBkZXIgVW50ZXJibMO2Y2tlIGdld2ljaHRldCBuYWNoIEZsw6RjaGUuCkZJWE1FIC0gYmVzc2VyIG5hY2ggQW56YWhsIGRlciBXb2huaMOkdXNlcj8KYGBge3J9CnNnYklJX2JsayA9IGludGVyc2VjdGlvbl9kYXRhICU+JQogIGdyb3VwX2J5KEJMSykgJT4lCiAgc3VtbWFyaXNlKHNnYklJdTY1PXN1bShzZ2JJSXU2NSphcmVhKS9zdW0oYXJlYSkvMTAwKQpgYGAKCgpgYGB7cn0KZ2dwbG90KGJyb29tOjp0aWR5KHNwVHJhbnNmb3JtKGJsa19pbl9iZXosIHdnczg0KSwgcmVnaW9uPSdCTEsnKSAlPiUgbGVmdF9qb2luKHNnYklJX2JsaywgYnk9YygnaWQnPSdCTEsnKSkpICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD1zZ2JJSXU2NSkpICsgY29vcmRfbWFwKCkKYGBgCgojIyBBZGphemVueiB2b24gQmzDtmNrZW4KCmBgYHtyfQpyb3cubmFtZXMoYmxrX2luX2JleikgPSBibGtfaW5fYmV6JEJMSwpidWZmZWRlZF9ibGtfaW5fYmV6ID0gZ0J1ZmZlcihibGtfaW5fYmV6LCBieWlkPVQsIHdpZHRoPTQwKQphZGphY2VuY3kgPSBnSW50ZXJzZWN0cyhnQnVmZmVyKGJsa19pbl9iZXosIGJ5aWQ9VCwgd2lkdGg9NDApLCBieWlkID0gVCkKcm93bmFtZXMoYWRqYWNlbmN5KSA9IGJsa19pbl9iZXokQkxLCmNvbG5hbWVzKGFkamFjZW5jeSkgPSBibGtfaW5fYmV6JEJMSwoKYWRqYWNlbmN5X2RmID0gYWRqYWNlbmN5ICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIG11dGF0ZShmcm9tPXJvd25hbWVzKC4pKSAlPiUKICBnYXRoZXIodG8sIGNvbm5lY3RlZCwgLWZyb20pICU+JSBmaWx0ZXIoY29ubmVjdGVkKSAlPiUKICBpbm5lcl9qb2luKHNwVHJhbnNmb3JtKGJsa19pbl9iZXosIHdnczg0KSAlPiUgY29vcmRpbmF0ZXMoKSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSByZW5hbWUoZnJvbV9sb25nPVYxLCBmcm9tX2xhdD1WMikgJT4lIG11dGF0ZShmcm9tPXJvd25hbWVzKC4pKSkgJT4lCiAgaW5uZXJfam9pbihzcFRyYW5zZm9ybShibGtfaW5fYmV6LCB3Z3M4NCkgJT4lIGNvb3JkaW5hdGVzKCkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgcmVuYW1lKHRvX2xvbmc9VjEsIHRvX2xhdD1WMikgJT4lIG11dGF0ZSh0bz1yb3duYW1lcyguKSkpCgpnZ3Bsb3QoKSArCiAgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGZpbGw9J2dyYXknLCBkYXRhPWJyb29tOjp0aWR5KHNwVHJhbnNmb3JtKGJsa19pbl9iZXosIHdnczg0KSwgcmVnaW9uPSdCTEsnKSkgKwogIGdlb21fc2VnbWVudChhZXMoeD1mcm9tX2xvbmcsIHk9ZnJvbV9sYXQsIHhlbmQ9dG9fbG9uZywgeWVuZD10b19sYXQpLCBzaXplPTAuMSwgY29sb3I9J2JsYWNrJywgZGF0YT1hZGphY2VuY3lfZGYpICsKICB0aGVtZV9ub3RoaW5nKCkgKyBjb29yZF9tYXAoKQoKZ2dzYXZlKCdmaWdzL2FkamFjZW5jeS5wZGYnKQphZGphY2VuY3lfZGYgJT4lIGZpbHRlcihjb25uZWN0ZWQgJiBmcm9tICE9IHRvKSAlPiUgc2VsZWN0KGZyb20sIHRvKSAlPiUgd3JpdGVfY3N2KCdhcHAvZGF0YS9hZGphY2VuY3kuY3N2JykKYGBgCgoKIyMgU2VsZWN0IHJlbGV2YW50IGRhdGEKCmBgYHtyfQpvcHRpbV9rYXBhcyA9IHJlbGV2YW50X2thcGFzCm9wdGltX2tpZHNfaW5fYmxrcyA9IGtpZHNfaW5fYmxrcyAlPiUgZmlsdGVyKGtpZHMgPiAwKSAlPiUgaW5uZXJfam9pbih0cmF2ZWxfbWF0cml4LCBieT0nQkxLJykgJT4lIHNlbGVjdChCTEssIGtpZHMpICU+JSBtdXRhdGUoa2lkcz1raWRzKQpucm93KG9wdGltX2tpZHNfaW5fYmxrcykKbnJvdyhvcHRpbV9rYXBhcykKCnNlbGVjdF9zY2hvb2xzID0gYXMuY2hhcmFjdGVyKG9wdGltX2thcGFzJFNjaHVsbnVtbWVyKQpzZWxlY3RfYmxrcyA9IGFzLmNoYXJhY3RlcihvcHRpbV9raWRzX2luX2Jsa3MkQkxLKQoKb3B0aW1fbWF0cml4ID0gaW5uZXJfam9pbihvcHRpbV9raWRzX2luX2Jsa3MsIHRyYXZlbF9tYXRyaXgsIGJ5PSdCTEsnKVtzZWxlY3Rfc2Nob29sc10KCmRpbShvcHRpbV9tYXRyaXgpCgpvcHRpbV9rYXBhcyRLYXBhICU+JSBzdW0Kb3B0aW1fa2lkc19pbl9ibGtzJGtpZHMgJT4lIHN1bQpgYGAKCiMjIE5haXZlIFp1b3JkbnVuZzogSmVkZXIgQmxvY2sgenVyIG7DpGNoc3RlbiBTY2h1bGUKCmBgYHtyfQpzb2x1dGlvbiA9IG9wdGltX21hdHJpeCAlPiUgbXV0YXRlKEJMSz1vcHRpbV9raWRzX2luX2Jsa3MkQkxLKSAlPiUgZ2F0aGVyKHNjaG9vbCwgZGlzdCwgLUJMSykgJT4lIGdyb3VwX2J5KEJMSykgJT4lIHRvcF9uKDEsIC1kaXN0KSAlPiUgdW5ncm91cAoKb3B0aW1fbWF0cml4ICU+JSB0ICU+JSBhcy5kYXRhLmZyYW1lICU+JSBzdW1tYXJpc2VfZWFjaChmdW5zKG1pbikpICU+JSBzdW0oKQoKc29saW5lcyA9IHJlX3NjaHVsc3RhbmRfZGYgJT4lIGlubmVyX2pvaW4oc29sdXRpb24sIGJ5PWMoJ3NwYXRpYWxfbmFtZSc9J3NjaG9vbCcpKSAlPiUgaW5uZXJfam9pbihjYmluZChhcy5kYXRhLmZyYW1lKGNvb3JkaW5hdGVzKGJsa1tibGskQkVaID09IGJlel9pZCxdKSksIGJsa1tibGskQkVaID09IGJlel9pZCxdQGRhdGFbJ0JMSyddKSkKCmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBsZWZ0X2pvaW4oc29sdXRpb24sIGJ5PWMoJ2lkJz0nQkxLJykpCmdnbWFwKG1hcCwgZGFya2VuID0gYygwLjUsICd3aGl0ZScpKSArIGdlb21fcG9seWdvbihhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXAsIGZpbGw9c2Nob29sKSwgZGF0YT1kYXRhKSArCiAgZ2VvbV9zZWdtZW50KGFlcyh4PVYxLHk9VjIseGVuZD1sb24seWVuZD1sYXQpLCBkYXRhPXNvbGluZXMsIHNpemU9MC4zKSArCiAgZ2VvbV9wb2ludChhZXMobG9uLCBsYXQpLCBjb2xvcj0nYmxhY2snLCBzaXplPTIsIGRhdGEgPSByZV9zY2h1bHN0YW5kX2RmICU+JSBpbm5lcl9qb2luKHNvbHV0aW9uLCBieT1jKCdzcGF0aWFsX25hbWUnPSdzY2hvb2wnKSkpICsKICBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCwgY29sb3I9c3BhdGlhbF9uYW1lKSwgZGF0YSA9IHJlX3NjaHVsc3RhbmRfZGYgJT4lIGlubmVyX2pvaW4oc29sdXRpb24sIGJ5PWMoJ3NwYXRpYWxfbmFtZSc9J3NjaG9vbCcpKSkgKwogIGNvb3JkX21hcCh4bGltPWMobWluKGRhdGEkbG9uZyktMC4wMSwgbWF4KGRhdGEkbG9uZykrMC4wMSksIHlsaW09YyhtaW4oZGF0YSRsYXQpLTAuMDEsIG1heChkYXRhJGxhdCkrMC4wMSkpICsKICBndWlkZXMoY29sb3I9RiwgZmlsbD1GKQpgYGAKCiMjIERhcnN0ZWxsdW5nIGRlciBadW9yZG51bmcgYWxzIFRhYmVsbGUKCmBgYHtyfQpsaWJyYXJ5KGZvcm1hdHRhYmxlKQpgYGAKCmBgYHtyfQpzb2x1dGlvbiAlPiUgaW5uZXJfam9pbihvcHRpbV9raWRzX2luX2Jsa3MsIGJ5PSdCTEsnKSAlPiUgaW5uZXJfam9pbih0cmF2ZWxfZnJvbV9ibGtzLCBieT1jKCdCTEsnPSdCTEsnLCAnc2Nob29sJz0nZHN0JykpICU+JQogIGdyb3VwX2J5KHNjaG9vbCkgJT4lIHN1bW1hcmlzZSgKICAgIGtpZHM9c3VtKGtpZHMpLAogICAgbnVtX2Jsb2Nrcz1uKCksCiAgICBtaW5fZGlzdD1taW4obWluKSwKICAgIGF2Z19kaXN0PW1lYW4oKGtpZHMqYXZnKS9zdW0oa2lkcykpLAogICAgbWF4X2Rpc3Q9bWF4KG1heCkKICApICU+JQogIGlubmVyX2pvaW4ocmVsZXZhbnRfa2FwYXMsIGJ5PWMoJ3NjaG9vbCc9J1NjaHVsbnVtbWVyJykpICU+JQogIG11dGF0ZSgKICAgIHV0aWxpemF0aW9uPWtpZHMvS2FwYQogICkgJT4lIHNlbGVjdCgKICAgU2NodWxlPXNjaG9vbCwKICAgYEJsw7Zja2VgPW51bV9ibG9ja3MsCiAgIEthcGF6aXTDpHQ9S2FwYSwKICAgS2luZGVyPWtpZHMsCiAgIEF1c2xhc3R1bmc9dXRpbGl6YXRpb24sCiAgIGBXZWcgKG1pbilgPW1pbl9kaXN0LAogICBgV2VnICjDmClgPWF2Z19kaXN0LAogICBgV2VnIChtYXgpYD1tYXhfZGlzdAogICkgJT4lCiAgZm9ybWF0dGFibGUoCiAgICBsaXN0KAogICAgICBLaW5kZXIgPSBmb3JtYXR0ZXIoInNwYW4iLCB4IH4gZGlnaXRzKHgsIDIpKSwKICAgICAgQXVzbGFzdHVuZyA9IGZvcm1hdHRlcigic3BhbiIsCiAgICAgICAgc3R5bGUgPSB4IH4gc3R5bGUoY29sb3IgPSBpZmVsc2UoeCA8IDEsICJncmVlbiIsICJyZWQiKSksCiAgICAgICAgeCB+IGljb250ZXh0KGlmZWxzZSh4IDwgMSwgIm9rIiwgInJlbW92ZSIpLCBwZXJjZW50KHgpKQogICAgICApLAogICAgICBgV2VnICjDmClgID0gcHJvcG9ydGlvbl9iYXIoImxpZ2h0Ymx1ZSIpLAogICAgICBgV2VnIChtaW4pYCA9IHByb3BvcnRpb25fYmFyKCJsaWdodGJsdWUiKSwKICAgICAgYFdlZyAobWF4KWAgPSBwcm9wb3J0aW9uX2JhcigibGlnaHRibHVlIikKICAgICkKICApCmBgYAoKCiMjIERhdGVuIGbDvHIgZGllIEFwcCBzcGVpY2hlcm4KCi0gZW50aXRpZXMuZ2VvanNvbgotIGVudGl0aWVzLmNzdgotIHVuaXRzLmdlb2pzb24KLSB1bml0cy5jc3YKLSB3ZWlnaHRzLmNzdgotIGFzc2lnbm1lbnQuY3N2CgojIyMgTmV1ZSBEYXRlbgoKCiMjIyMgRW50aXRpZXMgLyBTY2h1bGVuCgpgYGB7cn0KZW50aXRpZXMgPSBzdWJzZXQocmVfc2NodWxzdGFuZF9kZiwgc3BhdGlhbF9uYW1lICVpbiUgcmVsZXZhbnRfc2Nob29scykgJT4lCiAgcmVuYW1lKGVudGl0eV9pZCA9IHNwYXRpYWxfbmFtZSkgJT4lCiAgc2VsZWN0KC1nbWxfaWQsIC1zcGF0aWFsX2FsaWFzLCAtc3BhdGlhbF90eXBlKSAlPiUKICBpbm5lcl9qb2luKHJlbmFtZShyZWxldmFudF9rYXBhcywgY2FwYWNpdHk9S2FwYSksIGJ5PWMoJ2VudGl0eV9pZCc9J1NjaHVsbnVtbWVyJykpCmNvb3JkaW5hdGVzKGVudGl0aWVzKSA9IH4gbG9uICsgbGF0CnByb2o0c3RyaW5nKGVudGl0aWVzKSA9IENSUygiK3Byb2o9bG9uZ2xhdCArZWxscHM9V0dTODQgK2RhdHVtPVdHUzg0ICtub19kZWZzIikKZmlsZS5yZW1vdmUoJ2FwcC9kYXRhL2VudGl0aWVzLmdlb2pzb24nKQplbnRpdGllcyAlPiUgd3JpdGVPR1IoJ2FwcC9kYXRhL2VudGl0aWVzLmdlb2pzb24nLCBsYXllcj0iZW50aXRpZXMiLCBkcml2ZXI9Ikdlb0pTT04iLCBjaGVja19leGlzdHM9RikKZW50aXRpZXNAZGF0YSAlPiUgc2VsZWN0KGVudGl0eV9pZCwgY2FwYWNpdHkpICU+JSB3cml0ZV9jc3YoJ2FwcC9kYXRhL2VudGl0aWVzLmNzdicpCmBgYAoKIyMjIyBVbml0cyAvIFN0YXRpc3Rpc2NoZSBCbMO2Y2tlCgpgYGB7cn0KdW5pdHMgPSBzdWJzZXQoYmxrLCBCRVogPT0gYmV6X2lkKQoKdW5pdHNAZGF0YSRQTFIgPSBOVUxMCnVuaXRzQGRhdGEkRWludyA9IE5VTEwKdW5pdHNAZGF0YSRCRVogPSBOVUxMCnVuaXRzQGRhdGEkdW5pdF9pZCA9IHVuaXRzQGRhdGEkQkxLCnVuaXRzQGRhdGEkQkxLID0gTlVMTAoKdW5pdHMgPSB1bml0cyAlPiUgc3A6Om1lcmdlKAogIG9wdGltX2tpZHNfaW5fYmxrcyAlPiUKICAgIGxlZnRfam9pbihzZ2JJSV9ibGspICU+JQogICAgc2VsZWN0KHVuaXRfaWQ9QkxLLCBwb3B1bGF0aW9uPWtpZHMsIHNnYklJdTY1KQogICkKCmZpbGUucmVtb3ZlKCdhcHAvZGF0YS91bml0cy5nZW9qc29uJykKdW5pdHMgJT4lIHdyaXRlT0dSKCdhcHAvZGF0YS91bml0cy5nZW9qc29uJywgbGF5ZXI9InVuaXRzIiwgZHJpdmVyPSJHZW9KU09OIiwgY2hlY2tfZXhpc3RzPUYpCnVuaXRzQGRhdGEgJT4lIHdyaXRlX2NzdignYXBwL2RhdGEvdW5pdHMuY3N2JykKYGBgCgojIyMjIFp1b3JkbnVuZwoKYGBge3J9CmFzc2lnbm1lbnQgPSBzb2x1dGlvbiAlPiUgc2VsZWN0KHVuaXRfaWQ9QkxLLCBlbnRpdHlfaWQ9c2Nob29sKQphc3NpZ25tZW50ICU+JSB3cml0ZV9jc3YoJ2FwcC9kYXRhL2Fzc2lnbm1lbnQuY3N2JykKYGBgCgojIyMjIFdlaWdodHMgLyBTY2h1bHdlZ2UKCmBgYHtyfQp0cmF2ZWxfZnJvbV9ibGtzICU+JQogIHJlbmFtZSh1bml0X2lkPUJMSywgZW50aXR5X2lkPWRzdCkgJT4lCiAgI2dhdGhlcih3ZWlnaHQsIHZhbHVlLCAtdW5pdF9pZCwgLWVudGl0eV9pZCkgJT4lCiAgd3JpdGVfY3N2KCdhcHAvZGF0YS93ZWlnaHRzLmNzdicpCmBgYAoKIyMjIyBadXPDpHR6bGljaGUgRGF0ZW4KCmBgYHtyfQpmaWxlLmNvcHkoJ2Rvd25sb2FkL1JCU19PRF9CRVpfMjAxNV8xMi5nZW9qc29uJywgJ2FwcC9kYXRhL1JCU19PRF9CRVpfMjAxNV8xMi5nZW9qc29uJykKYGBgCgpgYGB7cn0KYWRkcmVzc2VzX2luX2JleiA9IEhLT18yMDE1WyFpcy5uYShvdmVyKEhLT18yMDE1LCBzdWJzZXQoYmxrLCBCRVogPT0gYmV6X2lkKSkkQkVaKSxjKCdTVE4nLCAnSE5SJyldCm5hbWVzKGFkZHJlc3Nlc19pbl9iZXopID0gYygnc3RyZWV0JywgJ25vJykKYWRkcmVzc2VzX2luX2JlekBkYXRhCgpmaWxlLnJlbW92ZSgnYXBwL2RhdGEvYWRkcmVzc2VzLmdlb2pzb24nKQp3cml0ZU9HUihhZGRyZXNzZXNfaW5fYmV6LCAiYXBwL2RhdGEvYWRkcmVzc2VzLmdlb2pzb24iLCBsYXllcj0iYWRkcmVzc2VzIiwgZHJpdmVyPSJHZW9KU09OIiwgY2hlY2tfZXhpc3RzID0gRkFMU0UpCmBgYAo=